﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CommonUtils;

namespace SearchBackEnd
{
    static class MessageHandler
    {
        private static CounterVector counterVector = new CounterVector();
        private static PageDatabase pageDB = new PageDatabase();
        private static String myGroupId = null;

        /// <summary>
        /// Adds a page to the DB
        /// </summary>
        /// <param name="p">the page to add</param>
        /// <param name="isPrimary">is this a primary server for this page</param>
        /// <param name="other">the other server that has this page</param>
        public static void AddPage(Page p, Boolean isPrimary, String other)
        {
            Print.ColorEmit("Adding page " + p.Name + " as " + (isPrimary ? "primary" : "secondary") + " server", Print.Foreground.Blue);
            if (pageDB.IsPageExists(p))
            {
                Print.ColorEmit("page already exists on this server",Print.Foreground.Red);
                return;
            }
            pageDB.AddPage(p, isPrimary, other);
            counterVector.IncCount(myGroupId, isPrimary);
        }

        /// <summary>
        /// Search for a page in page db - among the pages that this server has the primary copy.
        /// </summary>
        public static String[] Search(String[] keywords)
        {
            StringBuilder kw = new StringBuilder();
            foreach (String word in keywords)
                kw.Append(word + " ");
            Print.ColorEmit("Got search requst",Print.Foreground.Green);
            return pageDB.Search(keywords);
        }

        /// <summary>
        /// add a page to the back end index - finds the servers that has the least amount of pages and sends them an index request
        /// </summary>
        /// <param name="p"></param>
        public static void Index(Page p)
        {
            //find servers to send the page to.
            String primaryAddr = counterVector.GetMinCountEp(true);
            String secondaryAddr = counterVector.GetMinCountEp(primaryAddr, false);
            Print.ColorEmit("Got index requst for page " + p.Name, Print.Foreground.Green);
            //if this is the primary server - index the page here and inform all of the other servers.
            //otherwise send an index request
            //then do the same for the secondary server.
            if (primaryAddr.Equals(myGroupId))
            {
                AddPage(p, true, secondaryAddr);
                BEGroupCommunication.updateCount(counterVector.GetCounts(myGroupId));
            }
            else
            {
                BEGroupCommunication.sendIndexRequest(primaryAddr, p, true, secondaryAddr);
            }
            if (secondaryAddr.Equals(myGroupId))
            {
                AddPage(p, false, primaryAddr);
                BEGroupCommunication.updateCount(counterVector.GetCounts(myGroupId));
            }
            else
                BEGroupCommunication.sendIndexRequest(secondaryAddr, p, false, primaryAddr);
        }

        /// <summary>
        /// Adds new servers in view to counters vector
        /// </summary>
        /// <param name="newServers"></param>
        public static void ServersAdded(String[] newServers)
        {
            //Adding new servers to counters vector
            foreach (String server in newServers)
            {
                counterVector.CreateEntry(server);
            }
        }

        /// <summary>
        /// this method is called after a view change it:
        /// 1. removes the failed servers from the counter vector.
        /// 2. for each page checked if the other server that is holding this page has failed if so - sends this page to another server.
        /// </summary>
        /// <param name="failedServers"></param>
        public static void Recover(String[] failedServers)
        {
            //removing failed servers from counterVector
            foreach (String server in failedServers)
            {
                counterVector.RemoveCounts(server);
            }
            //moving lost pages to other servers
            foreach (String server in failedServers)
            {
                ServerFailed(server);
            }
            //end recovery mode
            BEGroupCommunication.endRecovery();
        }

        /// <summary>
        /// called when after a view change. 
        /// at this state it is possible that the number of pages per server is not balanced
        /// due to server failing and to new server that joined.
        /// the balance is done in two stages - first for the pages that are primary and then for pages that are secondary.
        /// this way we avoid sending both copies to the same server.
        /// </summary>
        /// <param name="isPrimary">balance primary page or secondary pages.</param>
        public static void BalanceIndex(Boolean isPrimary)
        {
            //get average count
            long avgPageCount = counterVector.GetAvarageCount(isPrimary);
            long myPageCount = counterVector.GetCounts(myGroupId, isPrimary);
            long myPageCount2 = counterVector.GetCounts(myGroupId, !isPrimary);

            //calculate # of pages to remove in order to get a good balance
            long numPagesToSend = Math.Max(myPageCount - avgPageCount, 0);
            Dictionary<Page, String> toSend = null;

            //remove & send pages
            if (numPagesToSend > 0)
            {
                toSend = pageDB.GetAllPages(isPrimary);
                Dictionary<Page, String>.Enumerator iter = toSend.GetEnumerator();
                while (numPagesToSend > 0 && iter.MoveNext())
                {
                    String destServer = counterVector.GetMinCountEp(iter.Current.Value, isPrimary, myPageCount - 1, myPageCount2);
                    if (destServer == null || destServer.Equals(myGroupId)) continue; //can't send page
                    //remove page
                    pageDB.RemovePage(iter.Current.Key);
                    //send page
                    BEGroupCommunication.sendIndexRequest(destServer, iter.Current.Key, isPrimary, iter.Current.Value);
                    //update other
                    BEGroupCommunication.updateOther(iter.Current.Value, iter.Current.Key, destServer);
                    counterVector.IncCount(destServer, isPrimary);
                    numPagesToSend--;
                    myPageCount--;
                }
            }

            //finish this phase
            if (isPrimary) BEGroupCommunication.endPrimaryBalance();
            else BEGroupCommunication.endSecondaryBalance();
        }

        /// <summary>
        /// sends an update to all the other servers of this server's page count.
        /// this is called after the primary and secondary balance phases.
        /// </summary>
        public static void UpdateMyCount()
        {
            CounterVector.VectorValues updatedCount = new CounterVector.VectorValues(pageDB.GetPageCount(true), pageDB.GetPageCount(false));

            //update counters
            counterVector.UpdateCounts(myGroupId, updatedCount);
            BEGroupCommunication.updateCount(updatedCount);

            counterVector.PrintVector();
        }

        /// <summary>
        /// update the count of a back end server in my count vector
        /// </summary>
        /// <param name="epAddr">the entry to update.</param>
        /// <param name="newCount">the new value.</param>
        public static void UpdateCount(String epAddr, CounterVector.VectorValues newCount)
        {
            counterVector.UpdateCounts(epAddr, newCount);
        }

        /// <summary>
        /// set my group ID - done right after the first view change.
        /// </summary>
        /// <param name="myGroupId"></param>
        public static void SetGroupID(String myGroupId)
        {
            MessageHandler.myGroupId = myGroupId;
            counterVector.CreateEntry(myGroupId);
        }

        /// <summary>
        /// updates the other field of a page, that can happen if a page is send from one server to another during the balance process
        /// </summary>
        /// <param name="p"></param>
        /// <param name="other"></param>
        public static void UpdatePageOther(Page p, String other)
        {
            pageDB.UpdatePageOther(p, other);
        }

        /// <summary>
        /// sending pages that where lost because of a server fail to another server
        /// </summary>
        /// <param name="serverId">the server that failed</param>
        private static void ServerFailed(String serverId)
        {
            //sending pages lost to another server
            Page[] pagesToBackup = pageDB.GetPages(serverId, false);
            foreach (Page p in pagesToBackup)
            {
                String dest = counterVector.GetMinCountEp(myGroupId, false);
                BEGroupCommunication.sendIndexRequest(dest, p, false, myGroupId);
                pageDB.UpdatePageOther(p, dest);
            }

            pagesToBackup = pageDB.GetPages(serverId, true);
            foreach (Page p in pagesToBackup)
            {
                String dest = counterVector.GetMinCountEp(myGroupId, true);
                BEGroupCommunication.sendIndexRequest(dest, p, true, myGroupId);
                pageDB.UpdatePageOther(p, dest);
            }
            counterVector.PrintVector();
        }
        
        /// <summary>
        /// increases the count of a specific entry in counter vector by one.
        /// </summary>
        /// <param name="epAddr"></param>
        /// <param name="isPrimary"></param>
        public static void IncCount(string epAddr, bool isPrimary)
        {
            counterVector.IncCount(epAddr, isPrimary);
        }
    }
}
